/*
 * BspPacket.js
 *
 * Copyright (C) 2012 - Dr.NP
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * BSP.Packet encode & decode ports for JavaScript
 * 
 * @package bsp
 * @author Dr.NP <np@bsgroup.org>
 * @update 10/15/2012
 * @changelog 
 * 		[10/25/2012] - Creation
 */

function BspPacket()
{
	this.BSP_VAL_INT8		= 0x1;
	this.BSP_VAL_INT16		= 0x2;
	this.BSP_VAL_INT32		= 0x3;
	this.BSP_VAL_INT64		= 0x4;
	this.BSP_VAL_BOOLEAN	= 0x5;
	this.BSP_VAL_FLOAT		= 0x11;
	this.BSP_VAL_DOUBLE		= 0x12;
	this.BSP_VAL_STRING		= 0x21;
	this.BSP_VAL_OBJECT		= 0x51;
	this.BSP_VAL_OBJECT_END	= 0x52;
	this.BSP_VAL_ARRAY		= 0x61;
	this.BSP_VAL_ARRAY_END	= 0x62;
	this.BSP_VAL_NULL		= 0x7F;
	
	this.last_cmd = 0;
	this.last_plen = 0;
	this.has_remaining = false;
	
	return;
}

BspPacket.prototype.pack = function (cmd, params) {
	var r = new ByteStream();
	r.set_int32(cmd);
	r.set_int64(0);
	
	this._ins_stream_object(params, r);
	// Write length;
	var len = r.data_len - 12;
	r.ua[8] = (len >> 24) & 0xFF;
	r.ua[9] = (len >> 16) & 0xFF;
	r.ua[10] = (len >> 8) & 0xFF;
	r.ua[11] = len & 0xFF;
	
	return r;
}

BspPacket.prototype.unpack = function (data) {
	var cmd;
	var plen;
	
	if (this.has_remaining)
	{
		cmd = this.last_cmd;
		plen = this.last_plen;
	}
	
	else
	{
		if (data.bytesAvailable() >= 12)
		{
			cmd = data.get_int32();
			plen = data.get_int64();
		}
		
		else
		{
			// Half
			return null;
		}
	}
	
	if (data.bytesAvailable() >= plen)
	{
		// Enough data to unserialize
		this.has_remaining = false;
		var params = this._unpack_object(data, plen);
		var ret = new Object();
		ret.command = cmd;
		ret.params = params;
		
		return ret;
	}
	
	else
	{
		this.last_cmd = cmd;
		this.last_plen = plen;
		this.has_remaining = true;
	}
	
	return null;
}

BspPacket.prototype._unpack_object = function(s) {
	var len_limit = arguments[1] ? parseInt(arguments[1]) : s.bytesAvailable();
	
	var r = new Object();
	var vtype;
	var key;
	var val;
	var is_key = true;
	var mark = s.cursor;
	
	while (s.cursor - mark < len_limit)
	{
		vtype = s.get_int8();
		
		if (is_key)
		{
			if (vtype == this.BSP_VAL_STRING)
			{
				val = s.get_int64();
				key = s.get_utfstring(val);
			}
			
			else if (vtype == this.BSP_VAL_OBJECT_END)
			{
				return r;
			}
			
			else
			{
				break;
			}
			
			is_key = false;
			continue;
		}
		
		else
		{
			// Value
			switch (vtype)
			{
				case this.BSP_VAL_INT8 : 
					r[key] = s.get_int8();
					break;
				case this.BSP_VAL_INT16 : 
					r[key] = s.get_int16();
					break;
				case this.BSP_VAL_INT32 : 
					r[key] = s.get_int32();
					break;
				case this.BSP_VAL_INT64 : 
					r[key] = s.get_int64();
					break;
				case this.BSP_VAL_BOOLEAN : 
					r[key] = (s.get_int8() == 0) ? false : true;
					break;
				case this.BSP_VAL_FLOAT : 
					r[key] = s.get_float();
					break;
				case this.BSP_VAL_DOUBLE : 
					r[key] = s.get_double();
					break;
				case this.BSP_VAL_STRING : 
					val = s.get_int64();
					r[key] = s.get_utfstring(val);
					break;
				case this.BSP_VAL_OBJECT : 
					r[key] = this._unpack_object(s);
					break;
				case this.BSP_VAL_ARRAY : 
					r[key] = this._unpack_array(s);
					break;
				case this.BSP_VAL_NULL : 
					r[key] = null;
					break;
				default : 
					break;
			}
			
			is_key = true;
		}
	}
	return r;
}

BspPacket.prototype._unpack_array = function(s) {
	var len_limit = arguments[1] ? parseInt(arguments[1]) : s.bytesAvailable();
	
	var r = new Array();
	var vtype;
	var key = 0;
	var val;
	var mark = s.cursor;
	
	while (s.cursor - mark < len_limit)
	{
		vtype = s.get_int8();
		switch (vtype)
		{
			case this.BSP_VAL_INT8 : 
				r[key] = s.get_int8();
				break;
			case this.BSP_VAL_INT16 : 
				r[key] = s.get_int16();
				break;
			case this.BSP_VAL_INT32 : 
				r[key] = s.get_int32();
				break;
			case this.BSP_VAL_INT64 : 
				r[key] = s.get_int64();
				break;
			case this.BSP_VAL_BOOLEAN : 
				r[key] = (s.get_int8() == 0) ? false : true;
				break;
			case this.BSP_VAL_FLOAT : 
				r[key] = s.get_float();
				break;
			case this.BSP_VAL_DOUBLE : 
				r[key] = s.get_double();
				break;
			case this.BSP_VAL_STRING : 
				val = s.get_int64();
				r[key] = s.get_utfstring(val);
				break;
			case this.BSP_VAL_OBJECT : 
				r[key] = this._unpack_object(s);
				break;
			case this.BSP_VAL_ARRAY : 
				r[key] = this._unpack_array(s);
				break;
			case this.BSP_VAL_ARRAY_END : 
				return r;
				break;
			case this.BSP_VAL_NULL : 
				r[key] = null;
				break;
			default : 
				break;
		}
		
		key ++;
	}
	
	return r;
}

BspPacket.prototype._ins_stream_object = function(input, stream) {
	for (var key in input)
	{
		stream.set_int8(this.BSP_VAL_STRING);
		stream.set_int64(key.length);
		stream.set_utfstring(key);
		
		switch (typeof(input[key]))
		{
			case 'number' : 
				if (input[key] == parseInt(input[key]))
				{
					// Integer
					stream.set_int8(this.BSP_VAL_INT32);
					stream.set_int32(input[key]);
				}
				
				else
				{
					// Double
					stream.set_int8(this.BSP_VAL_DOUBLE);
					stream.set_double(input[key]);
				}
				
				break;
			case 'boolean' : 
				stream.set_int8(this.BSP_VAL_BOOLEAN);
				stream.set_int8((input[key]) ? 1 : 0);
				break;
			case 'string' : 
				stream.set_int8(this.BSP_VAL_STRING);
				stream.set_int64(stream._real_len(input[key]));
				stream.set_utfstring(input[key]);
				break;
			case 'object' : 
				if (Array.isArray(input[key]))
				{
					stream.set_int8(this.BSP_VAL_ARRAY);
					this._ins_stream_array(input[key], stream);
					stream.set_int8(this.BSP_VAL_ARRAY_END);
				}
				
				else
				{
					stream.set_int8(this.BSP_VAL_OBJECT);
					this._ins_stream_object(input[key], stream);
					stream.set_int8(this.BSP_VAL_OBJECT_END);
				}
				break;
			default : 
				stream.set_int8(this.BSP_VAL_NULL);
				break;
		}
	}
	return;
}

BspPacket.prototype._ins_stream_array = function(input, stream) {
	for (var i = 0; i < input.length; i ++)
	{
		switch (typeof(input[i]))
		{
			case 'number' : 
				if (input[i] == parseInt(input[i]))
				{
					// Integer
					stream.set_int8(this.BSP_VAL_INT32);
					stream.set_int32(input[i]);
				}
				
				else
				{
					// Double
					stream.set_int8(this.BSP_VAL_DOUBLE);
					stream.set_double(input[i]);
				}
				
				break;
			case 'boolean' : 
				stream.set_int8(this.BSP_VAL_BOOLEAN);
				stream.set_int8((input[key]) ? 1 : 0);
				break;
			case 'string' : 
				stream.set_int8(this.BSP_VAL_STRING);
				stream.set_int64(stream._real_len(input[i]));
				stream.set_utfstring(input[i]);
				break;
			case 'object' : 
				if (Array.isArray(input[i]))
				{
					stream.set_int8(this.BSP_VAL_ARRAY);
					this._ins_stream_array(input[i], stream);
					stream.set_int8(this.BSP_VAL_ARRAY_END);
				}
				
				else
				{
					stream.set_int8(this.BSP_VAL_OBJECT);
					this._ins_stream_object(input[i], stream);
					stream.set_int8(this.BSP_VAL_OBJECT_END);
				}
				break;
			default : 
				stream.set_int8(this.BSP_VAL_NULL);
				break;
		}
	}
	return;
}
